﻿//-----------------------------------------------------------------------
// <copyright file="Navigation.cs" company="Microsoft Corporation">
//     Copyright Microsoft Corporation. All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) This is sample code only, do not use in production environments.
// </copyright>
//-----------------------------------------------------------------------
namespace Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;
    using Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp.MVVMSupport;
    using Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp.ViewModels;
    using Windows.UI.Xaml.Controls;

    /// <summary>
    /// Class that deals with the navigation events of the app.
    /// </summary>
    public static class Navigation
    {
        /// <summary>
        /// The namespace for where all view models can be found.
        /// </summary>
        private const string ViewModelNamespace = "Microsoft.ALMRangers.VsarTreasureMap.WindowsStoreApp.ViewModels.";

        /// <summary>
        /// Stores the navigation sequence of the user through the app.
        /// </summary>
        private static Stack<BackStackItem> backStack = new Stack<BackStackItem>();

        /// <summary>
        /// This contains the name (string representation) and the actual view model that is currently displayed.
        /// </summary>
        private static Tuple<string, object> currentViewModel;

        /// <summary>
        /// Gets a value indicating whether the application can navigate back based on back stack size.
        /// </summary>
        /// <value><c>true</c> if this instance can go back; otherwise, <c>false</c>.</value>
        public static bool CanGoBack
        {
            get
            {
                return backStack.Count >= 2;
            }
        }

        /// <summary>
        /// Gets or sets the Root Frame used for navigation.
        /// </summary>
        /// <value>The root frame.</value>
        public static Frame RootFrame { get; set; }

        /// <summary>
        /// Attempts to navigate back based on what is in the back stack
        /// </summary>
        public static void GoBack()
        {
            if (!CanGoBack)
            {
                return;
            }

            backStack.Pop(); // remove current page 
            var last = backStack.Pop();
            Navigate(last);
        }

        /// <summary>
        /// Attempts to navigate the root frame to a new page.
        /// </summary>
        /// <param name="page">Type of page to navigate to.</param>
        /// <param name="parameter">Parameter to pass to the constructor of a page to be navigated to.</param>
        /// <param name="addToBackStack">True for should add to the back stack.</param>
        public static void Navigate(Type page, object parameter = null, bool addToBackStack = true)
        {
            Navigate(page, parameter, null, addToBackStack);
        }

        /// <summary>
        /// Reloads the current page
        /// </summary>
        internal static void ReloadCurrentPage()
        {
            var last = backStack.Peek();
            Navigate(last, false);
        }

        /// <summary>
        /// Navigates the specified back stack item.
        /// </summary>
        /// <param name="backStackItem">The back stack item.</param>
        /// <param name="addToBackStack">if set to <c>true</c> [add to back stack].</param>
        private static void Navigate(BackStackItem backStackItem, bool addToBackStack = true)
        {
            Navigate(backStackItem.Page, backStackItem.Parameter, backStackItem.State, addToBackStack);
        }

        /// <summary>
        /// Navigates the specified page.
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="state">The state.</param>
        /// <param name="addToBackStack">if set to <c>true</c> [add to back stack].</param>
        private static void Navigate(Type page, object parameter, object state, bool addToBackStack)
        {
            if (backStack.Count > 0)
            {
                var currentStatefulPage = RootFrame.Content as IPageState;
                var currentPageState = default(object);
                if (currentStatefulPage != null)
                {
                    currentPageState = currentStatefulPage.GetState();
                    backStack.Peek().State = currentPageState;
                }
            }

            if (addToBackStack)
            {
                backStack.Push(new BackStackItem(page, parameter));
            }

            var name = page.Name;
            var viewModel = ViewModelNamespace + name;
            if (currentViewModel == null || currentViewModel.Item1 != viewModel)
            {
                var currentPage = RootFrame.Content as Page;
                if (currentPage != null)
                {
                    var share = currentPage.DataContext as IShare;
                    if (share != null)
                    {
                        share.Unregister();
                    }
                }

                currentViewModel = Tuple.Create(viewModel, GetViewModel(viewModel, parameter));
            }

            var @namespace = page.Namespace;
            var mode = Windows.UI.ViewManagement.ApplicationView.Value.ToString();

            var modePage = @namespace + "." + mode + "." + name;

            var pageType = Type.GetType(modePage);
            if (pageType == null)
            {
                pageType = page;
            }

            if (parameter == null)
            {
                RootFrame.Navigate(pageType);
            }
            else
            {
                RootFrame.Navigate(pageType, parameter);
            }

            (RootFrame.Content as Page).DataContext = currentViewModel.Item2;
            var newPageState = RootFrame.Content as IPageState;
            if (newPageState != null && state != null)
            {
                newPageState.SetState(state);
            }
        }

        /// <summary>
        /// With the provided view model class name and any parameters - it will create an instance of that view model.
        /// </summary>
        /// <param name="viewModelName">The class name of the view model to create.</param>
        /// <param name="parameter">The parameter of the view model constructor.</param>
        /// <returns>The instance of the view model</returns>
        private static object GetViewModel(string viewModelName, object parameter = null)
        {
            var viewModelType = Type.GetType(viewModelName);
            var paramType = default(Type);

            if (parameter != null)
            {
                paramType = parameter.GetType();
            }

            var constructors = viewModelType.GetTypeInfo().DeclaredConstructors;
            var constructorInfo = default(ConstructorInfo);

            foreach (var item in constructors)
            {
                var parameters = item.GetParameters();
                if (parameter != null && parameters.Length > 0)
                {
                    if (parameters[0].ParameterType == paramType)
                    {
                        constructorInfo = item;
                        break;
                    }
                }
                else
                {
                    constructorInfo = item;
                }
            }

            if (constructorInfo.GetParameters().Length > 0)
            {
                return constructorInfo.Invoke(new object[] { parameter });
            }
            else
            {
                return constructorInfo.Invoke(new object[] { });
            }
        }

        /// <summary>
        /// Defines an item on the back stack.
        /// </summary>
        private class BackStackItem
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="BackStackItem"/> class.
            /// </summary>
            /// <param name="page">The page.</param>
            /// <param name="parameter">The parameter.</param>
            public BackStackItem(Type page, object parameter)
            {
                this.Page = page;
                this.Parameter = parameter;
            }

            /// <summary>
            /// Gets the page.
            /// </summary>
            /// <value>Gets the page.</value>
            public Type Page { get; private set; }

            /// <summary>
            /// Gets the parameter.
            /// </summary>
            /// <value>Gets the parameter.</value>
            public object Parameter { get; private set; }

            /// <summary>
            /// Gets or sets the state.
            /// </summary>
            /// <value>The state.</value>
            public object State { get; set; }            
        }
    }
}